package dfsketches.earth_crust_gen;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.imageio.ImageIO;

import dfsketches.bicycles.Packed3dArray;
import dfsketches.bicycles.Readable3dStructure;
import dfsketches.bicycles.Structure3d;
import dfsketches.terrain_generator.ComplexCosineGenerator;
import dfsketches.visualizer_2d.VisualizerWindow;

public class EarthCrustGen {

	/**
	 * @param args
	 */
	//TODO Убрать долой все final.
	public static void main(String[] args) {
		final Random rnd = new Random();
		final int w = 256;	//x,y size
		final int h = 256;	//z size
		
//		Structure3d vox = new RegularArray(w, w, h);
//		Structure3d vox = new SparseVoxelOctotree(w, 0xFF40B0B0);
		Structure3d vox = new Packed3dArray(w, w, h, 5);
//		Structure3d vox = new Overpacked3dArray(w, w, h, 5);
		
		int[][] level = genSurface(w, 20f, 20f, 5f, false);
		
		for (int x = 0; x < w; x++)
			for (int y = 0; y < w; y++)
				vox.put(x, y, 0, level[x][y], 0xFFA04040);
		System.out.println("Magma added.");
		
		final int bassinN = 200;				//Количество бассейнов
		final float min_bassin_r = 40;			//Минимальный радиус бассейна
		final float bassin_r_fluct = 80;		//Разброс радиусов бассейна
		final float min_bassin_h = 30;			//Минимальная высота бассейна
		final float bassin_h_fluct = 20;		//Разброс высот басссейна
		final int bassin_border_complexity = 3;	//Сложность границы (на виде сверху) бассейна.
		float[] bassin_border_coeff = new float[bassin_border_complexity];
		final float bassin_border_fluc = 2f;	//Разброс колебаний границы бассейна
		final float PI4 = (float)Math.PI / 4;
		
		final float x_cut_k = 0.6f;		//Коэффициенты "обрезки". Нижняя часть полусферы срезается,
		final float z_cut_k = 0.7f;		//величина срезанного определяется z_cut_k.
										//Чтобы не считать лишние клетки, где толщина "блина"
										//заведомо меньше 0, использую x_cut_k.
										//Из-за возможных проблем округления желательно z_cut_k^2 + x_cut_k^2 > 1.
		
		for (int i = 0; i < bassinN; i++) {
			int bassin_color = 0xFF000000 | ((rnd.nextInt(30) > 0) ? (0x00010101 * (rnd.nextInt(8)*16 + 64)) : rnd.nextInt(0x01000000));
			float r = rnd.nextFloat() * bassin_r_fluct + min_bassin_r;
			float bh = rnd.nextFloat() * bassin_h_fluct + min_bassin_h;
			
			for (int k = 0; k < bassin_border_complexity; k++)
				bassin_border_coeff[k] = rnd.nextFloat() * bassin_border_fluc;
			
			int sx = (int)(r * x_cut_k);
			int cx = rnd.nextInt(w + 2*sx) - sx;
			int cy = rnd.nextInt(w + 2*sx) - sx;
			
			int x_s = Math.max(cx-sx, 0);
			int x_f = Math.min(cx+sx, w-1);
			for (int x = x_s; x <= x_f; x++) {
				float argx = (x-cx)/r;
				int sy = (int)(r * Math.sqrt(1 - argx*argx));
				float f_dy = 0;
				for (int k = 0; k < bassin_border_complexity; k++) {
					f_dy += bassin_border_coeff[k] * Math.sin(k * x * PI4);
				}
				int dy = (int)f_dy;

				int y_s = Math.max(cy - sy + dy, 0);
				int y_f = Math.min(cy + sy + dy, w-1);
				for (int y = y_s; y <= y_f; y++) {
					float argy = (float)(y-cy-dy) / sy;
					int ch = (int)((Math.sqrt(1 - argy * argy) - z_cut_k) * bh);
					if (ch > 0) {
						int new_level = Math.min(level[x][y] + ch, h-1);
						if (level[x][y] != new_level) {
							vox.put(x, y, level[x][y], new_level, bassin_color);
							level[x][y] = new_level;
						}
					}
				}//for y
			}//for x
			System.out.println("Bassin #" + (i+1) + " generated.");
		}
		
		for (int x = 0; x < w; x++)
			for (int y = 0; y < w; y++)
				vox.put(x, y, level[x][y], h, 0xFF40B0B0);
		System.out.println("Sky added");
		
		visualizeFrontView(w, h, vox, 30);
//		outputFrontView(w, h, vox, 30, "out.png");
	}
	
	@SuppressWarnings("unused")
	public static void visualizeFrontView(int w, int h, Readable3dStructure vox, int slice) {
		int[][] bitmap = new int[h][w];
		for (int y = 0; y < w; y++)
			for (int z = 0; z < h; z++)
				bitmap[h-1 - z][y] = vox.get(slice, y, z);
		VisualizerWindow viz = new VisualizerWindow(bitmap, 200, 200);
	}
	
	@SuppressWarnings("unused")
	public static void visualizeTopView(int w, Readable3dStructure vox, int slice) {
		int[][] bitmap = new int[w][w];
		for (int x = 0; x < w; x++)
			for (int y = 0; y < w; y++)
				bitmap[x][y] = vox.get(slice, y, x);
		VisualizerWindow viz = new VisualizerWindow(bitmap, 200, 200);
	}
	
	public static void outputFrontView(int w, int h, Readable3dStructure vox, int slice, String filename) {
		BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		for (int y = 0; y < w; y++)
			for (int z = 0; z < h; z++)
				img.setRGB(y, h-1 - z, vox.get(slice, y, z));
		try {
			ImageIO.write(img, "png", new File(filename));
		} catch (IOException e) {
			e.printStackTrace();
		}
//		img.setRGB(startX, startY, w, h, rgbArray, offset, scansize)
	}
	
	private static int[][] genSurface(int size, float init_height, float magnitude, float magnitude_k,
			boolean fixed_borders) {
		ComplexCosineGenerator gen = new ComplexCosineGenerator();
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("init_height", init_height);
		param.put("magnitude", magnitude);
		param.put("magnitude_k", magnitude_k);
		param.put("fixed_borders", fixed_borders);
		return gen.generate(size, param);
	}
}
